package net.animats.cli;

import java.io.*;
import java.lang.*;
import java.util.List;
import java.util.regex.Matcher;
import java.util.ArrayList;
import jline.*;

public class Parser {

	enum ParserError { UNKNOWN_COMMAND, OBJECT_NOT_FOUND, NOT_INTEGER, OBJECT_WRONG_TYPE, ALL_OKAY };

	public static final int version = 1;

	// THE LIST OF COMMANDS THAT THE PARSER UNDERSTANDS
	private List<Verb> verbs = new ArrayList<Verb>();

	// THE LIST OF OBJECTS THAT COMMANDS CAN REFER TO
	private List<Selectable> selectables = new ArrayList<Selectable>();

	private ConsoleReader reader;
	private SimpleCompletor completor;

	public static void main (String[] args) {
		Parser parser = new Parser();
		parser.Start();
	}

	public Parser () {
		// CREATE THE JlINE ConsoleReader
		try {
			reader = new ConsoleReader();
			reader.setBellEnabled(false);
			reader.setDebug(new PrintWriter(new FileWriter("writer.debug", true)));
		}
		catch (IOException e) {
			System.out.println("ERROR: Can't open JLine ConsoleReader.");
		}

		// ADD THE STANDARD BUILT-IN VERBS

		addVerb (
			new Verb("quit") {
				public int doVerb(Object[] _objects, Parser _parser) {
					System.out.println("Exiting program.\n");
					System.exit(0);
					return (0);
				}
			}  
		);

		addVerb (
			new Verb("help") {
				public int doVerb(Object[] _objects, Parser _parser) {
					System.out.println("Available commands:");
					for (Verb verb : verbs) {
						System.out.println("   " + verb.helpText());
					}

					return (0);
				}
			}  
		);

		addVerb (
			new Verb("objects") {
				public int doVerb(Object[] _objects, Parser _parser) {
					System.out.println("Selectable objects:");
					for (Selectable selectable : selectables) {
						System.out.println("   " + selectable.getLabel() + " " + selectable.getType());
					}

					return (0);
				}
			}  
		);

		completor = new SimpleCompletor (GetCandidateStrings());
		reader.addCompletor(completor);
	}

	public String[] GetCandidateStrings() {
		List<String> labels = new ArrayList<String>();

		for (Selectable selectable : selectables) {
			labels.add(selectable.getLabel());
		}	

		return (String[]) labels.toArray(new String[0]);	
	}

	public void Start() {
		BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));

		String command;
		Matcher matcher;

		boolean exiting = false;
		boolean matchFound = false;
		ParserError error = ParserError.ALL_OKAY;
		int errorIndex;

		Object[] objects;

		try {
			do {
				completor.setCandidateStrings(GetCandidateStrings());
				command = reader.readLine("\n> ");

				matchFound = false;
				errorIndex = 0;
				error = ParserError.ALL_OKAY;

				for (Verb verb : verbs) {
					matcher = verb.getMatcher(command);
					if (matcher.find()) {
						matchFound = true;
						int groupCount = matcher.groupCount();

						if (groupCount != 0) {
							// OBJECT REFERENCES WERE FOUND
							objects = new Object[groupCount];		
							for (int index = 0; index < groupCount && error == ParserError.ALL_OKAY; index++) {
								// RESOLVE THESE LABELS INTO THEIR OBJECTS AND STORE
								// REFERENCES TO THOSE OBJECTS SO THEY CAN BE PASSED
								// TO THE doVerb METHOD.	
								objects[index] = matcher.group(index + 1);
								if (verb.getArgumentType(index).equals("{integer}")) {
									// PARSE INTEGER AND REPLACE object[index]
									try {
										Integer result = Integer.parseInt((String) objects[index]);
										objects[index] = result;
									} 
									catch (NumberFormatException exception) {
										error = ParserError.NOT_INTEGER;
										errorIndex = index;
									}

								} else if (verb.getArgumentType(index).equals("{string}")) {
									// STRINGS ARE JUST STORED VERBATIM
								} else {
									// ALL REMAINING ARGUMENTS ARE PRESUMED TO BE A TYPE OF SELECTABLE

									boolean notFound = true;

									for (Selectable selectable : selectables) {
										if (selectable.getLabel().equals((String) objects[index])) {
											//System.out.println("found object: " + selectable.getLabel() + " " + selectable.getType());
											// AN OBJECT WITH THE RIGHT LABEL HAS BEEN FOUND,
											// MAY YET STILL BE OF THE WRONG TYPE
											notFound = false;

											if (selectable.getType().equals(verb.getArgumentType(index))) {
												// SET THE OBJECT TO BE PASSED TO THE VERB FROM
												// THE STRING CONTAINING THE OBJECT'S LABEL TO
												// A REFERENCE TO THE OBJECT ITSELF

												objects[index] = selectable;
												// NOT SET error BACK TO OKAY JUST IN CASE AN OBJECT
												// OF THE WRONG TYPE WAS FOUND FIRST
												error = ParserError.ALL_OKAY;
												break;
											} else {
												// NOTE THAT A MATCH WAS FOUND OF THE WRONG TYPE
												// KEEP SEARCHING FOR ANOTHER MATCH THAT MAY BE
												// OF THE RIGHT TYPE, BUT PRINT A 'TYPE MISMATCH'
												// ERROR IF NONE IS FOUND
												error = ParserError.OBJECT_WRONG_TYPE;
												continue;
											}			
										}
									}

									if (notFound) {
										error = ParserError.OBJECT_NOT_FOUND;
									}
								}
								
								// THERE IS AN ERROR, SO REMEMBER THE WORD THAT CAUSED THE ERROR
								if (error != ParserError.ALL_OKAY) {
									errorIndex = index;
								}
							}	
						} else {
							// THIS VERB DOESN'T REFER TO ANY OBJECTS
							objects = null;
						}

						if (error == ParserError.ALL_OKAY) {
							verb.doVerb(objects, this);
						} else {
							// DISPLAY THE APPROPRIATE ERROR MESSAGE

							switch (error) {
								case OBJECT_WRONG_TYPE:
									System.out.println("ERROR: Object \"" + objects[errorIndex] + "\" is not of the expected type \"" + verb.getArgumentType(errorIndex) + "\".");
									break;
								case NOT_INTEGER:
									System.out.println ("ERROR: Expected an integer and got \"" + objects[errorIndex] + "\".");
									break;
								case OBJECT_NOT_FOUND:
									System.out.println("ERROR: Object \"" + objects[errorIndex] + "\" is not an object of the type \"" + verb.getArgumentType(errorIndex) + "\".");
									break;
							}
						}
					}
				}

				if (matchFound == false) {
					System.out.println("ERROR: Unknown command \"" + command + "\".");
				}

			} while (!exiting);
		} catch ( IOException readError ) {
			System.err.println( "ERROR: Can't read stdin: " + readError);
		}

	}

	public void addSelectable (Selectable _selectable) {
		selectables.add(_selectable);
	}

	public void addVerb(Verb _verb) {
		verbs.add(_verb);
	}

}

